
if SpyUtils then return end
SpyUtils = {Version=1.0}

local min = math.min
local max = math.max
local floor = math.floor
local ceil = math.ceil
local format = string.format

-- This can and should be overridden
Error = print

function printargs(...)
   local args = {...}
   print('NumArgs: ' .. #args)
   for i,v in ipairs(args) do
      if dump then
         dump(v, i)
      else
         print('[i]='..tostring(v)..' ('..type(v)..')')
      end
   end
end

SlashCmdList.PRINT_STUFF = function(args)
   if args and args:len() > 0 then
      -- local code = 'local ret = {'..args..'};'
      -- code = code .. 'for i,v in ipairs(ret) do print("["..i.."]:", v) end'
      local code = 'printargs('..args..')'
      assert(loadstring(code))()
   end

   -- for w in string.gmatch(s, "%a+") do

   -- local list = args:gsub('(%s+)', ',')
   -- assert(loadstring('print(' .. list .. ')', 'printstuff'))()
end

SLASH_PRINT_STUFF1 = '/printstuff'
SLASH_PRINT_STUFF2 = '/print'

-------------------------------------------------------------------------------
function try(func, ...)
   local success, msg = pcall(func, ...)
   if not success then
      Error('Script error: '..msg)
   end
   return success
end

-------------------------------------------------------------------------------
function TableSize(t)
   local n = 0
   for k, v in pairs(t) do
      n = n + 1
   end
   return n
end

tcount = TableSize

function tcopy(t)
   if not t then return nil end
   local copy = {}
   for k,v in pairs(t) do
      copy[k] = v
   end
   return copy
end

function tcompare(a, b)
   if a == b then return true end
   if a==nil or b==nil then return false end
   for k,v in pairs(a) do
      if b[k] ~= v then
         return false
      end
   end
   return true
end

-------------------------------------------------------------------------------
function round(x)
   return floor(x + 0.5)
end

function clamp(x, lo, hi)
   return max(lo, min(hi, x))
end

-------------------------------------------------------------------------------
function RGBToHex(r, g, b)
	return format("%02x%02x%02x", r, g, b)
end

function RGBPercToHex(r, g, b)
	return format("%02x%02x%02x", r*255, g*255, b*255)
end

function RGBAToHex(r, g, b, a)
	return format("%02x%02x%02x%02x", a or 255, r, g, b)
end

function RGBToHexChecked(r, g, b)
	r = r <= 255 and r >= 0 and r or 0
	g = g <= 255 and g >= 0 and g or 0
	b = b <= 255 and b >= 0 and b or 0
	return format("%02x%02x%02x", r, g, b)
end

function RGBPercToHexChecked(r, g, b)
	r = r <= 1 and r >= 0 and r or 0
	g = g <= 1 and g >= 0 and g or 0
	b = b <= 1 and b >= 0 and b or 0
	return format("%02x%02x%02x", r*255, g*255, b*255)
end

function RGBAToHexChecked(r, g, b, a)
	r = r <= 255 and r >= 0 and r or 0
	g = g <= 255 and g >= 0 and g or 0
	b = b <= 255 and b >= 0 and b or 0
   a = a <= 255 and a >= 0 and a or 0
	return format("%02x%02x%02x%02x", a, r, g, b)
end

function RGBToStr(r, g, b)
   return format('|cff%02x%02x%02x', r, g, b)
end

function RGBAToStr(r, g, b, a)
   return format('|c%02x%02x%02x%02x', a or 255, r, g, b)
end

function GetColoredText(text, r, g, b, a)
   return format('|c%02x%02x%02x%02x%s', a or 255, r, g, b, text)
end

-------------------------------------------------------------------------------
local registry = {}
local frame = CreateFrame('Frame')

local function UnregisterOrphanedEvent(event)
    if not next(registry[event]) then
        registry[event] = nil
        frame:UnregisterEvent(event)
    end
end

local function OnEvent(self, event, ...)
    for listener,val in pairs(registry[event]) do
        local success, rv = listener[1](self, ...)
        if rv then
            registry[event][listener] = nil
            if not success then --Hack.ScriptError('event callback', rv) end
               assert(type(rv) == 'string')
               Error(
                  'Script error in event handler\n'..
                  '  Event: %s\n'..
                  '  Error: %s',
                  event, rv
               )
            end
        end
    end        
    UnregisterOrphanedEvent(event)
end

frame:SetScript('OnEvent', OnEvent)

-------------------------------------------------------------------------------

function AddEventListener (event, callback, userparam)
    assert(callback, 'invalid callback')
    if not registry[event] then
        registry[event] = {}
        frame:RegisterEvent(event)
    end
    local listener = { callback, userparam }
    registry[event][listener] = true
    return listener
end

function RemoveEventListener (event, listener)
    registry[event][listener] = nil
    UnregisterOrphanedEvent(event)
end

function AddEventListeners(listeners)
   for k, v in pairs(listeners) do
      AddEventListener(k, v)
   end
end

-------------------------------------------------------------------------------
function MergeTables(dest, src)
   if src == nil then return nil end
   if dest == nil then dest = {} end
   for k, v in pairs(src) do
      if type(v) == 'table' then
         dest[k] = MergeTables(dest[k], v)
      else
         if dest[k] == nil then
            dest[k] = v
         end
      end
   end
   return dest
end

function InitSavedVar(varName, defaults)
   _G[varName] = MergeTables(_G[varName], defaults)
end

-------------------------------------------------------------------------------
function Toggle(f)
   if f:IsShown() then f:Hide()
   else f:Show()
   end
end

function ResetFrame(f)
   f:ClearAllPoints()
   f:SetScale(1.0)
   f:SetPoint('TOPLEFT', 100, -100)
end

function MakeFrameDraggable(f, movable, resizable)
   if not (movable or resizable) then return end
   f:EnableMouse(true)
   f:SetMovable(movable or false)
   f:SetResizable(resizable or false)
   f:RegisterForDrag('LeftButton')
   f:SetScript('OnDragStart', function()
      if f:IsResizable() and IsAltKeyDown() then
         f:StartSizing(f:GetPoint(1))
      elseif f:IsMovable() then
         f:StartMoving()
      end
   end)
   f:SetScript('OnDragStop', function() f:StopMovingOrSizing() end)
end

function MakeFrameResizable(f)
   f:EnableMouse(true)
   f:SetResizable(true)
end

function DisableDrag(f)
   f:EnableMouse(false)
   f:SetMovable(false)
   f:SetScript('OnDragStart', nil)
   f:SetScript('OnDragStop', nil)
end

SlashCmdList.TOGGLE_FRAME = function(name)
   local f = _G[name]
   if f then
      print('Toggling frame: ', name)
      Toggle(f)
   else
      print('Frame could not be found: ', name)
   end
end

SLASH_TOGGLE_FRAME1 = '/toggleframe'
SLASH_TOGGLE_FRAME2 = '/toggle'

-------------------------------------------------------------------------------
-- Returns nil if spell is not known
-- Returns highest rank spell ID if rank is not included in spell param
function GetSpellID(spell)
   local name, rank = GetSpellName(spell)
   if not name then return nil end
   local i = 1
   while true do
      local n, r = GetSpellName(i, BOOKTYPE_SPELL)
      assert(n) -- This should NEVER be null since we already got name above
      if n == name and r == rank then
         return i
      end
      i = i + 1
   end
end

function GetHighestRankSpellName(spell)
   local name, rank = GetSpellName(spell)
   if not name then return nil end
   rank = GetHighestSpellRank(name)
   if rank and rank ~= '' then
      name = name..'('..rank..')'
   end
   return name
end

function GetHighestSpellRank(spell)
   local name, rank = GetSpellName(spell)
   if not name then return nil end
   return select(2, GetSpellName(name))
end

function IsSpellKnown(spell)
   return GetSpellName(spell) ~= nil
end

local realSpellIDLookup = {}
function GetRealSpellID(spell)
   local id = realSpellIDLookup[spell]
   if id ~= nil then return id end
   for i = 1, 100000 do
      name = GetSpellInfo(i)
      if name == spell then
         realSpellIDLookup[spell] = i
         return i
      end
   end
end

-------------------------------------------------------------------------------
function HookTooltip(onEvent, callback)
   local lineAdded = false
   GameTooltip:HookScript(onEvent, function(tooltip, ...)
      if not lineAdded then
         callback(tooltip, ...)
         lineAdded = true
      end
   end)
   GameTooltip:HookScript('OnTooltipCleared', function(tooltip, ...)
      lineAdded = false
   end)
end

-------------------------------------------------------------------------------
local function AutoEventHandler(self, event, ...)
   if self.events[event](self, ...) then
      self:UnregisterEvent(event)
   end
end

function RegisterEvents(widget, events)
   if events == nil then return end
   widget.events = events
   for k, v in pairs(events) do
      widget:RegisterEvent(k)
   end
   widget:SetScript("OnEvent",
      function(self, event, ...)
         if self.events[event](self, ...) then
            self:UnregisterEvent(event)
         end
      end)
end

function RegisterHandlers(widget, handlers)
   if handlers == nil then return end
   widget.handlers = handlers
   for k, v in pairs(handlers) do
      widget:SetScript(k, v)
   end
end

-------------------------------------------------------------------------------
function GetStatusColor(x, lo, hi)
   x = clamp(x, lo, hi)
   local pct = x / hi
   local r, g
   if pct > 0.5 then -- green to yellow
      r = 1.0 - ((pct - 0.5) * 2)
      g = 1
   else
      r = 1
      g = pct * 2
   end
   return r, g, 0
end

function GetPctColor(pct)
   local r, g
   local pct = clamp(pct, 0, 1)
   if pct > 0.5 then -- green to yellow
      r = 1.0 - ((pct - 0.5) * 2)
      g = 1
   else
      r = 1
      g = pct * 2
   end
   return r, g, 0
end

-------------------------------------------------------------------------------
function GetFileName(fontPath)
   return fontPath:match('[^\\]+$')
end

-------------------------------------------------------------------------------
local bit_band = bit.band
function FlagCheck(flags1, flags2)
	return bit_band(flags1, flags2) == flags2
end

-------------------------------------------------------------------------------
if not Deformat then
   -- if AceLibrary then
      -- Deformat = AceLibrary("Deformat-2.0")
   -- end

   if not Deformat then
      Deformat = {}

      do
         local sequences = {
            ["%d*d"] = "%%-?%%d+",
            ["s"] = ".+",
            ["[fg]"] = "%%-?%%d+%%.?%%d*",
            ["%%%.%d[fg]"] = "%%-?%%d+%%.?%%d*",
            ["c"] = ".",
         }
         local curries = {}
         
         local function donothing() end
         
         local select = select
         
         local function concat(left, right, ...)
            if not right then
               return left
            elseif select('#', ...) >= 1 then
               return concat(concat(left, right), ...)
            end
            if not left:find("%%1%$") and not right:find("%%1%$") then
               return left .. right
            elseif not right:find("%%1%$") then
               local i
               for j = 9, 1, -1 do
                  if left:find("%%" .. j .. "%$") then
                     i = j
                     break
                  end
               end
               while true do
                  local first
                  local firstPat
                  for x, y in pairs(sequences) do
                     local i = right:find("%%" .. x)
                     if not first or (i and i < first) then
                        first = i
                        firstPat = x
                     end
                  end
                  if not first then
                     break
                  end
                  i = i + 1
                  right = right:gsub("%%(" .. firstPat .. ")", "%%" .. i .. "$%1")
               end
               return left .. right
            elseif not left:find("%%1%$") then
               local i = 0
               while true do
                  local first
                  local firstPat
                  for x, y in pairs(sequences) do
                     local i = left:find("%%" .. x)
                     if not first or (i and i < first) then
                        first = i
                        firstPat = x
                     end
                  end
                  if not first then
                     break
                  end
                  i = i + 1
                  left = left:gsub("%%(" .. firstPat .. ")", "%%" .. i .. "$%1")
               end
               return concat(left, right)
            else
               local i
               for j = 9, 1, -1 do
                  if left:find("%%" .. j .. "%$") then
                     i = j
                     break
                  end
               end
               local j
               for k = 9, 1, -1 do
                  if right:find("%%" .. k .. "%$") then
                     j = k
                     break
                  end
               end
               for k = j, 1, -1 do
                  right = right:gsub("%%" .. k .. "%$", "%%" .. k + i .. "%$")
               end
               return left .. right
            end
         end
         
         local function bubble(f, i, a1, ...)
            if not a1 then
               return
            end
            if f[i] then
               return tonumber(a1), bubble(f, i+1, ...)
            else
               return a1, bubble(f, i+1, ...)
            end
         end
         
         local function bubbleNum(f, o, i, ...)
            if i > select('#', ...) then
               return
            end
            if f[i] then
               return tonumber((select(o[i], ...))), bubbleNum(f, o, i+1, ...)
            else
               return (select(o[i], ...)), bubbleNum(f, o, i+1, ...)
            end
         end
         
         local function Curry(...)
            local pattern = concat(...)
            local unpattern = '^' .. pattern:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") .. '$'
            local f = {}
            if not pattern:find("%%1%$") then
               local i = 0
               while true do
                  local first
                  local firstPat
                  for x, y in pairs(sequences) do
                     local i = unpattern:find("%%%%" .. x)
                     if not first or (i and i < first) then
                        first = i
                        firstPat = x
                     end
                  end
                  if not first then
                     break
                  end
                  unpattern = unpattern:gsub("%%%%" .. firstPat, "(" .. sequences[firstPat] .. ")", 1)
                  i = i + 1
                  if firstPat == "c" or firstPat == "s" then
                     f[i] = false
                  else
                     f[i] = true
                  end
               end
               
               if i == 0 then
                  return donothing
               else
                  return function(text)
                     return bubble(f, 1, text:match(unpattern))
                  end
               end
            else
               local o = {}
               local i = 1
               while true do
                  local pat
                  for x, y in pairs(sequences) do
                     if not pat and unpattern:find("%%%%" .. i .. "%%%$" .. x) then
                        pat = x
                        break
                     end
                  end
                  if not pat then
                     break
                  end
                  unpattern = unpattern:gsub("%%%%" .. i .. "%%%$" .. pat, "(" .. sequences[pat] .. ")", 1)
                  if pat == "c" or pat  == "s" then
                     f[i] = false
                  else
                     f[i] = true
                  end
                  i = i + 1
               end
               i = 1
               pattern:gsub("%%(%d)%$", function(w) o[i] = tonumber(w); i = i + 1; end)
               
               if i == 1 then
                  return donothing
               else
                  return function(text)
                     return bubbleNum(f, o, 1, text:match(unpattern))
                  end
               end
            end
         end
         
         function Deformat:Deformat(text, a1, ...)
            local pattern = (''):join(a1, ...)
            if curries[pattern] == nil then
               curries[pattern] = Curry(a1, ...)
            end
            return curries[pattern](text)
         end
      end

      local mt = getmetatable(Deformat) or {}
      mt.__call = Deformat.Deformat
      setmetatable(Deformat, mt)
   end
end

-------------------------------------------------------------------------------


